#ifndef _NE_VECTOR2_H_
#define _NE_VECTOR2_H_

#include <mathematics/Math.h>

namespace ne
{
    class NEAPI Vector2
    {
        public:
            inline Vector2()
                : x(0.0f), y(0.0f)
            {
            }

            inline explicit Vector2(const real v)
                : x(v), y(v)
            {
            }

            inline Vector2(const Vector2 &v)
                : x(v.x), y(v.y)
            {
            }

            inline explicit Vector2(const real* const v)
                : x(v[0]), y(v[1])
            {
            }

            inline Vector2(const real vx, const real vy)
                : x(vx), y(vy)
            {
            }

            inline void swap(Vector2 &v)
            {
                std::swap(x, v.x);
                std::swap(y, v.y);
            }

            inline real operator [] (const size_t v) const
            {
                assert(v < 2);
                return *(&x + v);
            }

            inline real& operator [] (const size_t v)
            {
                assert(v < 2);
                return *(&x + v);
            }

            inline real* ptr()
            {
                return &x;
            }

            inline const real* ptr() const
            {
                return &x;
            }

            // Evaluate
            inline Vector2& operator = (const real v)
            {
                x = v;
                y = v;
                return *this;
            }

            inline Vector2& operator = (const Vector2 &v)
            {
                x = v.x;
                y = v.y;
                return *this;
            }

            // Equal
            inline bool operator == (const Vector2 &v) const
            {
                return (x == v.x) && (y == v.y);
            }

            // Inequivalence
            inline bool operator != (const Vector2 &v) const
            {
                return (x != v.x) || (y != v.y);
            }

            inline bool operator < (const Vector2 &v) const
            {
                return (x < v.x) && (y < v.y);
            }

            inline bool operator > (const Vector2 &v) const
            {
                return (x > v.x) && (y > v.y);
            }

            // Addition
            inline Vector2 operator + (const real v) const
            {
                return Vector2(x + v, y + v);
            }

            inline Vector2 operator + (const Vector2 &v) const
            {
                return Vector2(x + v.x, y + v.y);
            }

            NEAPI friend Vector2 operator + (const real vl, const Vector2 &vr);

            // Subtration
            inline Vector2 operator - (const real v) const
            {
                return Vector2(x - v, y - v);
            }

            inline Vector2 operator - (const Vector2 &v) const
            {
                return Vector2(x - v.x, y - v.y);
            }

            NEAPI friend Vector2 operator - (const real vl, const Vector2 &vr);

            // Multiplication
            inline Vector2 operator * (const real v) const
            {
                return Vector2(x * v, y * v);
            }

            inline Vector2 operator * (const Vector2 &v) const
            {
                return Vector2(x * v.x, y * v.y);
            }

            NEAPI friend Vector2 operator * (const real vl, const Vector2 &vr);

            // Division
            inline Vector2 operator / (const real v) const
            {
                assert(v != 0.0f);
                return Vector2(x / v, y / v);
            }

            inline Vector2 operator / (const Vector2 &v) const
            {
                assert((v.x != 0.0f) && (v.y != 0.0f));
                return Vector2(x / v.x, y / v.y);
            }

            NEAPI friend Vector2 operator / (const real vl, const Vector2 &vr);

            // Self updates
            inline Vector2& operator += (const real v)
            {
                x += v;
                y += v;
                return *this;
            }

            inline Vector2& operator += (const Vector2 &v)
            {
                x += v.x;
                y += v.y;
                return *this;
            }

            inline Vector2& operator -= (const real v)
            {
                x -= v;
                y -= v;
                return *this;
            }

            inline Vector2& operator -= (const Vector2 &v)
            {
                x -= v.x;
                y -= v.y;
                return *this;
            }

            inline Vector2& operator *= (const real v)
            {
                x *= v;
                y *= v;
                return *this;
            }

            inline Vector2& operator *= (const Vector2 &v)
            {
                x *= v.x;
                y *= v.y;
                return *this;
            }

            inline Vector2& operator /= (const real v)
            {
                assert(v != 0.0f);
                x /= v;
                y /= v;
                return *this;
            }

            inline Vector2& operator /= (const Vector2 &v)
            {
                assert((v.x != 0.0f) && (v.y != 0.0f));
                x /= v.x;
                y /= v.y;
                return *this;
            }

            // Signed
            inline const Vector2& operator + () const
            {
                return *this;
            }

            inline Vector2 operator - () const
            {
                return Vector2(-x, -y);
            }

            inline real length() const
            {
                return std::sqrt((x * x) + (y * y));
            }

            inline real squaredLength() const
            {
                return (x * x) + (y * y);
            }

            // Get distance
            inline real distance(const Vector2 &v) const
            {
                return Vector2(x - v.x, y - v.y).length();
            }

            // Get square distance
            inline real squaredDistance(const Vector2 &v) const
            {
                return Vector2(x - v.x, y - v.y).squaredLength();
            }

            // Get a unit Vector2 with the same direction
            inline Vector2 getUnit() const
            {
                real len = length();
                if (len > 0.0f)
                {
                    real invLen = 1.0f / len;
                    return Vector2(x * invLen, y * invLen);
                }
                return ZERO;
            }

            // Normalize the Vector2
            inline real normalize()
            {
                real len = length();
                if (len > 0.0f)
                {
                    real invLen = 1.0f / len;
                    x *= invLen;
                    y *= invLen;
                }
                return len;
            }

            inline real dotProduct(const Vector2 &v) const
            {
                return (x * v.x) + (y * v.y);
            }

            inline real absDotProduct(const Vector2 &v) const
            {
                return std::abs(x * v.x) + std::abs(y * v.y);
            }

            inline real crossProduct(const Vector2 &v) const
            {
                return (x * v.y) - (y * v.x);
            }

            inline real angle(const Vector2 &v) const
            {
                real a = length();
                real b = v.length();
                if (a > 0.0f && b > 0.0f)
                {
                    return std::acos(dotProduct(v) / (a * b));

                }
                return 0.0f;
            }

            inline void makeFloor(const Vector2 &v)
            {
                if (x > v.x) x = v.x;
                if (y > v.y) y = v.y;
            }

            inline void makeCeil(const Vector2 &v)
            {
                if (x < v.x) x = v.x;
                if (y < v.y) y = v.y;
            }

            inline Vector2 perpendicular() const
            {
                return Vector2(-y, x);
            }

            // TODO: n is normal
            inline Vector2 reflect(const Vector2 &n) const
            {
                return 2 * dotProduct(n) * n - *this;
            }

            // Function for writing to a stream.
            NEAPI friend std::ostream& operator << (std::ostream &o, const Vector2 &v);

            inline ~Vector2()
            {
            }

        public:
            static bool Equal(const Vector2 &vS, const Vector2 &vD, const real tolerance=Math::EPSILON);

            static Vector2 Mid(const Vector2 &vS, const Vector2 &vD);

            static Vector2 Lerp(const Vector2 &vS, const Vector2 &vD, const real u);

            static Vector2 Bezier(const Vector2 &vS, const Vector2 &vA, const Vector2 &vB, const Vector2 &vD, const real u);

        public:
            static const Vector2 ZERO;
            static const Vector2 ONE;
            static const Vector2 UNIT_X;
            static const Vector2 UNIT_Y;
            static const Vector2 NEGATIVE_ONE;
            static const Vector2 NEGATIVE_UNIT_X;
            static const Vector2 NEGATIVE_UNIT_Y;

        public:
            real x, y;
    };
}
#endif
